/**
 * @file
 * @brief Provide notification of file system change
 *
 * Change notification consists of creation, deletion, modification and
 * rename.
 *
 * @bug There are race conditions that can cause events to be missed. For
 * example, when a directory is created, a sub directory is created under
 * it before it is added to watching; then, the creation event of sub directory
 * is missed. This is probably not fixable.
 */

#ifndef _FSNOTIFY_H
#define _FSNOTIFY_H

#include <fsnotify_def.h>
#include <types.h>
#include <common.h>
#include <list.h>
#include <glib.h>

/**
 * @brief Directory entry being watched
 */
struct dentry {
    uint32_t inode; /**< Virtual inode, or primary key in syncup database */
    char *name;
    struct dentry *parent;
    GTree *child;

    /* <private> */
    watch_desc_t wd;
};

extern struct dentry *dentry_new(const char *name);
extern void dentry_free(struct dentry *d);
extern void dentry_set_inode(struct dentry *d, uint32_t inode);
extern void dentry_add_child(struct dentry *d, struct dentry *child);
extern void dentry_move_child(struct dentry *from, struct dentry *to, struct dentry *child);
extern struct dentry *dentry_steal_child(struct dentry *d, const char *name);

static inline void
dentry_rename(struct dentry *d, const char *new_name)
{
    free(d->name);
    d->name = strdup(new_name);
}

static inline void
dentry_remove_child(struct dentry *d, const char *name)
{
    g_tree_remove(d->child, name);
}

/**
 * @brief Look up child dentry by name
 * @param d  dentry to look up
 * @param name  dentry name to be looked up
 * @return  Pointer to dentry if found; otherwise NULL
 */
static inline struct dentry *
dentry_lookup_child(struct dentry *d, const char *name)
{
    g_assert(d != NULL);

    /* Due to unfixable bug of event miss, d->child may be NULL because it has
     * no child and create event lost.
     */
    if (d->child == NULL) {
        return NULL;
    }
    return (struct dentry *) g_tree_lookup(d->child, name);
}

extern struct dentry *dentry_lookup_subtree(struct dentry *d, const char *path);

/**
 * @brief Get full path of dentry
 * @param d  dentry
 * @param path  \c GString to store path; if NULL, a new \c GString is created
 * @return  \c GString containing full path of dentry
 */
extern GString *dentry_get_path(struct dentry *d, GString *path);


/**
 * @brief Notification event structure
 */
struct fsnotify_event {
    uint32_t mask;
    char *name; /**< Name of changed file/directory */
    char *new_name; /**< New name of renamed file/directory if any */
    union {
        /** Dentry of the directory containing the changed file/directory */
        struct dentry *dir;
        struct {
            struct dentry *from;
            struct dentry *to;
        } rename;
    } d;
    /**
     * Dentry of the directory which is deleted or moved out
     *
     * @note At this point, this dentry is already removed from the dentry
     * tree, so you can't use dentry_lookup_child() to get it.
     */
    struct dentry *garbage;

    /* <private> */
    struct list_head evt_list;
};

struct fsnotify_desc;

typedef void (*fsnotify_evt_hdlr_t)(struct fsnotify_event *);

typedef struct dentry *dentry_builder_t(struct fsnotify_desc *);

extern void fsnotify_init(dentry_builder_t builder);
extern void fsnotify_end();
extern void fsnotify_register_handler(fsnotify_evt_hdlr_t func);
extern void fsnotify_unregister_handler(fsnotify_evt_hdlr_t func);

extern struct dentry *fsnotify_get_root();
extern struct fsnotify_desc *fsnotify_get_desc();
extern void fsnotify_inode_map_update(struct dentry *d);
extern struct dentry *fsnotify_indoe_to_dentry(uint32_t inode);

/**
 * @name Private internal functions */
/*@{*/
/**
 * @brief Watch dentry
 * @param desc  fsnotify descriptor
 * @param d  dentry to watch
 * @param path  dentry's path; can call dentry_get_path() to get it
 * @return 0 if success, -1 on error
 */
extern int fsnotify_watch_dentry(struct fsnotify_desc *desc, struct dentry *d, GString *path);
/**
 * @brief Watch dentry recursively
 *
 * This functions recursively descends into directory represented by \p d and
 * created whole dentry tree rooted at \p d.
 * @param desc  fsnotify descriptor
 * @param d  root dentry to watch
 * @param path  root dentry's path; can call dentry_get_path() to get it
 * @return 0 if success, -1 on error
 * @note All sub denties' inodes are initialized as 0
 */
extern int fsnotify_watch_dentry_recursively(struct fsnotify_desc *desc, struct dentry *d, GString *path);
extern void fsnotify_rm_watch_dentry(struct fsnotify_desc *desc, struct dentry *d);
extern void fsnotify_rm_watch_dentry_recursively(struct fsnotify_desc *desc, struct dentry *d);
/*@}*/

/**
 * @internal
 * @name Functions for testing purpose
 */
/*@{*/
extern struct dentry *fsnotify_wach_dir_recursively(struct fsnotify_desc *desc, const char *path);
extern void fsnotify_init_with_path(const char *path);
extern void fsnotify_print_dentry_tree();
/*@}*/

#endif /* ! _FSNOTIFY_H */
